/**
  ******************************************************************************
  * @file    command_line.c
  * @author  ashuai0110
  * @version V2.3
  * @date    2024-07-01
  * @brief   命令行交互
  *          (类命令行,空格分割 比如:cmd arg1 arg2\r\n或AT+CMD= arg1 arg2\r\n)
  *
  ******************************************************************************
  * @attention
  *
  * 版权声明:内容为编者(ashuai0110)原创,使用请注明出处,当然,你也可以不这样做^_^
  * 出处链接:https://gitee.com/ashuai0110/mcu_reuse_development_module.git
  *
  * Change Logs:
  * Date          Author        Notes
  * 2023-10-27    ashuai0110    完成基本内容
  * 2024-01-01    ashuai0110    修改链表挂起逻辑
  *
  ******************************************************************************
  */

/* 包含头文件-----------------------------------------------------------------*/
#include "command_line.h"

/**
 * @addtogroup modules
 * @{
 */

/**
 * @defgroup command_line command_line
 * @{
 */

/* 私有宏定义-----------------------------------------------------------------*/
/**
 * @defgroup command_line_local_macros command line local macros
 * @{
 */

/**
 * @defgroup command_line_check_parameters_validity command line check parameters validity
 * @{
 */
/* 命令行回显标志有效性检查 */
#define IS_VALID_ECHO_FLAG(x)                                                 \
(       ((x) == CMD_LINE_ECHO_ON)      ||                                     \
        ((x) == CMD_LINE_ECHO_OFF))

/* 命令行运行标志有效性检查 */
#define IS_VALID_RUN_FLAG(x)                                                  \
(       ((x) == CMD_LINE_RUN_START)    ||                                     \
        ((x) == CMD_LINE_RUN_STOP))

/* 命令行命令名称长度有效性检查 */
#define IS_VALID_NAME_LENGTH(x)                                               \
(       ((x) >= 1u)                    ||                                     \
        ((x) <= CMD_LINE_NAME_MAX))
/**
 * @}
 */

/**
 * @}
 */

/* 私有类型定义---------------------------------------------------------------*/

/* 私有变量-------------------------------------------------------------------*/
/**
 * @defgroup command_line_local_variables command line local variables
 * @{
 */

static cmd_line_t *pCmdLineHead = NULL;               /*!< 命令行链表头节点 */
static cmd_line_item_t cmdNameArr[CMD_LINE_SAVE_MAX]; /*!< 命令名称列表 */

/**
 * @}
 */

/* 全局变量-------------------------------------------------------------------*/

/* 私有函数原型---------------------------------------------------------------*/
/**
 * @defgroup command_line_local_functions command line local functions
 * @{
 */

/**
  * @brief  help命令处理
  *
  * @note   列举所有命令名称
  *
  * @param  _cmd : 命令行实例
  *
  * @param  argc : 参数个数
  *
  * @param  argv : 参数数据
  *
  * @retval None
  */
static void cmd_line_help(cmd_line_t *_cmd, int argc, char *argv[])
{
    uint16_t cnt, len;
    
    cmd_line_printf(_cmd, "cmd name list:\n");
    /* 输出所有命令名 */
    for(cnt = 0; cnt < CMD_LINE_SAVE_MAX; cnt++)
    {
        len = strlen(cmdNameArr[cnt].cmdName);
        /* 长度不为0才输出 */
        if(len)
        {
            cmd_line_printf(_cmd, "->%s\n", cmdNameArr[cnt].cmdName);
        } /* 长度为0直接退出 因为命令名的存放是连续的 */
        else
        {
            return ;
        }
    }
}

/**
  * @brief  以指定分割符分割字符串
  *
  * @note   pSrc = "cmd 1 2 3", 空格分割
  *         pBuf[0] = "cmd"
  *         pBuf[1] = "1"
  *         pBuf[2] = "2"
  *         pBuf[3] = "3"
  *
  * @param  pBuf : 分解后存放的缓存区
  *
  * @param  pSrc : 源数据
  *
  * @param  flg  : 单字节分割符
  *
  * @param  num  : 期望分割数量
  *
  * @retval 0:错误  !0:实际分割数量
  */
static uint16_t cmd_line_split(char *pBuf[], char *pSrc, char flg, uint16_t num)
{
    uint16_t cnt = 0;
    
    ASSERT_PARAM(IS_VALID_POINT(pBuf));
    ASSERT_PARAM(IS_VALID_POINT(pSrc));
    
    /* 缓存区先存放第一个字符串首地址 */
    pBuf[cnt++] = pSrc;
    while(1)
    {
        /* 若当前字符与参数flg相同 */
        if(*pSrc == flg)
        {
            *pSrc = '\0'; /* 当前字符改为'\0' 代表字符串到此结束 */
            /* 若分割数量达到参数num则break */
            if(cnt >= num)
            {
                break;
            }
            pBuf[cnt++] = pSrc + 1; /* 缓存区存放下一个字符串首地址 */
        } /* 若当前字符与结束符1或结束符2或'\0'相同则break */
        else if(CMD_LINE_END_SYMBOL1 == *pSrc || CMD_LINE_END_SYMBOL2 == *pSrc|| '\0' == *pSrc)
        {
            *pSrc = '\0';
            break;
        }
        pSrc++; /* 当前字符地址自增 */
    }
    
    return cnt;
}

/**
  * @brief  搜索命令名称
  *
  * @param  name : 命令名称
  *
  * @param  size : 命令名称长度
  *
  * @retval 0:未找到 !0:找到的位置
  */
static uint16_t cmd_line_search(char *name, uint16_t size)
{
    uint16_t cnt, len;
    
    /* 遍历整个命令存放数组 */
    for(cnt = 0; cnt < CMD_LINE_SAVE_MAX; cnt++)
    {
        /* 获取命令名长度 长度不为0说明存有命令名 */
        len = strlen(cmdNameArr[cnt].cmdName);
        if(len)
        {
            /* 先比较长度 长度相同再对比字符串 */
            if(len == size)
            {
                if(0 == strncmp(cmdNameArr[cnt].cmdName, name, size))
                {
                    return cnt + 1;
                }
            }
        }
        else
        {
            break;
        }
    }
    
    return 0;
}

/**
  * @brief  命令解析主要处理
  *
  * @param  _cmd : 命令行实例
  *
  * @retval None
  */
static void cmd_line_handle(cmd_line_t *_cmd)
{
    char *argv[CMD_LINE_ARG_MAX + 1] = {0};
    uint16_t argc, index;
    
    /* 输入回显 */
    if(CMD_LINE_ECHO_ON == _cmd->echoFlg)
    {
        cmd_line_printf(_cmd, "%s\r\n", _cmd->dataArr);
    }
    /* 分割命令名和各参数 */
    argc = cmd_line_split(argv, _cmd->dataArr, CMD_LINE_SPLIT_SYMBOL, CMD_LINE_ARG_MAX + 1);
    if(0 == argc)
    {
        cmd_line_printf(_cmd, "command split error!");
        return ;
    }
    /* 搜索命令名 */
    index = cmd_line_search(argv[0], strlen(argv[0]));
    if(0 == index)
    {
        cmd_line_printf(_cmd, "command not found!");
        return ;
    }
    /* 执行处理 */
    cmdNameArr[index - 1].cmdCallbackFn(_cmd, argc - 1, &argv[1]);
}

/**
 * @}
 */

/**
 * @defgroup command_line_global_functions command line global functions
 * @{
 */

/**
  * @brief  命令行初始化
  *
  * @note   对接数据读写接口,默认添加help命令
  *
  * @param  _cmd               : 命令行实例
  *
  * @param  writeFn            : 数据写接口
  *
  * @param  readFn             : 数据读接口
  *
  * @param  echoFlg            : 回显标志 @ref cmd_line_echo_flg
  *   @arg  CMD_LINE_ECHO_ON   : 开启回显
  *   @arg  CMD_LINE_ECHO_OFF  : 关闭回显
  *
  * @param  runFlg             : 运行标志 @ref cmd_line_run_flg
  *   @arg  CMD_LINE_RUN_START : 运行
  *   @arg  CMD_LINE_RUN_STOP  : 不运行
  *
  * @retval uint8_t
  *    @arg RET_OK             : 成功
  *    @arg RET_ERR            : 失败
  */
uint8_t cmd_line_init(cmd_line_t *_cmd, cmd_line_rw_fn_t writeFn, cmd_line_rw_fn_t readFn, uint8_t echoFlg, uint8_t runFlg)
{
    ASSERT_PARAM(IS_VALID_POINT(_cmd));
    ASSERT_PARAM(IS_VALID_POINT(writeFn));
    ASSERT_PARAM(IS_VALID_POINT(readFn));
    ASSERT_PARAM(IS_VALID_ECHO_FLAG(echoFlg));
    
    /* 初始化各参数 */
    memset(_cmd, 0, sizeof(cmd_line_t));
    _cmd->writeFn = writeFn;
    _cmd->readFn = readFn;
    _cmd->echoFlg = echoFlg;
    _cmd->runFlg = runFlg;
    /* 默认注册一个help命令 */
    cmd_line_register("help", cmd_line_help);
    /* 初始化时就插入链表 */
    return cmd_line_insert(_cmd);
}

/**
  * @brief  命令行取消初始化
  *
  * @param  _cmd : 命令行实例
  *
  * @retval None
  */
void cmd_line_de_init(cmd_line_t *_cmd)
{
    ASSERT_PARAM(IS_VALID_POINT(_cmd));
    
    /* 从链表取下并置零所有参数 */
    cmd_line_pending(_cmd);
    memset(_cmd, 0, sizeof(cmd_line_t));
}

/**
  * @brief  命令行插入工作链表
  *
  * @param  _cmd    : 命令行实例
  *
  * @retval uint8_t
  *    @arg RET_OK  : 成功
  *    @arg RET_ERR : 已存在(已被插入)
  */
uint8_t cmd_line_insert(cmd_line_t *_cmd)
{
    cmd_line_t *cmdLineTarget;
    
    ASSERT_PARAM(IS_VALID_POINT(_cmd));

    cmdLineTarget = pCmdLineHead; /* 拿到链表头节点 */
    /* 遍历查重 重复则返回错误 */
    while(cmdLineTarget)
    {
        if(cmdLineTarget == _cmd) { return RET_ERR; }
        cmdLineTarget = cmdLineTarget->pNext;
    }
    /* 否则使用头插法(后来居上)将此节点插入链表 */
    _cmd->pNext = pCmdLineHead;
    pCmdLineHead = _cmd;

    return RET_OK;
}

/**
  * @brief  命令行挂起(从工作链表中取下)
  *
  * @param  _cmd    : 命令行实例
  *
  * @retval uint8_t
  *    @arg RET_OK  : 成功
  *    @arg RET_ERR : 不存在(已被挂起)
  */
uint8_t cmd_line_pending(cmd_line_t *_cmd)
{
    cmd_line_t **cmdLineTarget;
    
    ASSERT_PARAM(IS_VALID_POINT(_cmd));

    cmdLineTarget = &pCmdLineHead; /* 先拿到头节点的地址 */
    /* 遍历寻找此节点 */
    while(*cmdLineTarget != _cmd)
    {
        if(NULL == *cmdLineTarget) { return RET_ERR; }
        cmdLineTarget = &(*cmdLineTarget)->pNext;
    }
    *cmdLineTarget = _cmd->pNext; /* 将此节点指针改为下一节点指针 即移除此节点 */

    return RET_OK;
}

/**
  * @brief  命令注册
  *
  * @note   注册命令名称及其处理回调函数
  *
  * @param  name     : 命令名称
  *
  * @param  callback : 命令处理回调函数
  *
  * @retval uint8_t
  *    @arg RET_OK   : 成功
  *    @arg RET_ERR  : 名称重名(命令已存在)
  *    @arg RET_FULL : 命令已满
  */
uint8_t cmd_line_register(const char *name, cmd_line_callback_fn_t callback)
{
    uint16_t cnt, len;
    
    ASSERT_PARAM(IS_VALID_NAME_LENGTH(strlen(name)));
    ASSERT_PARAM(IS_VALID_POINT(callback));
    
    /* 遍历整个命令存放数组 */
    for(cnt = 0; cnt < CMD_LINE_SAVE_MAX; cnt++) 
    {
        /* 先获取当前命令存放地长度 */
        len = strlen(cmdNameArr[cnt].cmdName);
        /* 有长度需查重 */
        if(len)
        {
            /* 先对比长度是否符合 符合才比较字符串 */
            if(len == strlen(name))
            {
                /* 字符串相同则返回重复错误 */
                if(0 == strcmp(cmdNameArr[cnt].cmdName, name))
                {
                    return RET_ERR;
                }
            }
        } /* 无长度便可直接存放 */
        else
        {
            strcpy(cmdNameArr[cnt].cmdName, name);
            cmdNameArr[cnt].cmdCallbackFn = callback;
            
            return RET_OK;
        }
    }
    
    return RET_FULL;
}

/**
  * @brief  命令行格式化输出
  *
  * @note   一次调用可输入最大长度由CMD_LINE_LEN_MAX决定
  *
  * @param  _cmd : 命令行实例
  *
  * @retval None
  */
void cmd_line_printf(cmd_line_t *_cmd, const char *format, ...)
{
    va_list args;
    int len;
    char buf[CMD_LINE_LEN_MAX];
    
    va_start(args, format);
    len = vsnprintf(buf, sizeof(buf), format, args);
    va_end(args);
    /* 以上内容为printf固定写法 以下内容对接底层接口 */
    _cmd->writeFn(buf, len);
}

/**
  * @brief  命令行交互轮询处理
  *
  * @note   轮询周期无要求
  *
  * @param  None
  *
  * @retval None
  */
void cmd_line_poll(void)
{
    static cmd_line_t* cmdLineTarget;
    static char buf[32] = {0};
    static uint8_t cnt, len;
    
    /* 遍历链表 */
    for(cmdLineTarget = pCmdLineHead; cmdLineTarget; cmdLineTarget = cmdLineTarget->pNext)
    {
        /* 运行标志是开启运行的才执行 */
        if(CMD_LINE_RUN_START != cmdLineTarget->runFlg) { continue; }
        /* 读取数据并记录读出长度 */
        len = cmdLineTarget->readFn(buf, sizeof(buf));
        /* 逐字节判断 */
        for(cnt = 0; cnt < len; cnt++)
        {
            /* 遇到结束符1或结束符2或'\0'认为是完整的数据 */
            if(CMD_LINE_END_SYMBOL1 == buf[cnt] || CMD_LINE_END_SYMBOL2 == buf[cnt] || '\0' == buf[cnt])
            {
                cmdLineTarget->dataArr[cmdLineTarget->dataCnt] = '\0'; /* 添加字符串结束符 */
                if(1 < cmdLineTarget->dataCnt)
                {
                    cmd_line_handle(cmdLineTarget); /* 执行处理 */
                }
                cmdLineTarget->dataCnt = 0;
            } /* 否则则循环写入数据暂存区 */
            else
            {
                cmdLineTarget->dataArr[cmdLineTarget->dataCnt++] = buf[cnt];
                if(CMD_LINE_LEN_MAX <= cmdLineTarget->dataCnt)
                {
                    cmdLineTarget->dataCnt = 0;
                }
            }
        }
    }
}

/**
  * @brief  设置命令行回显标志
  *
  * @param  _cmd              : 命令行实例
  *
  * @param  echoFlg           : 回显标志 @ref cmd_line_echo_flg
  *   @arg  CMD_LINE_ECHO_ON  : 开启回显
  *   @arg  CMD_LINE_ECHO_OFF : 关闭回显
  *
  * @retval None
  */
void cmd_line_set_echo_flg(cmd_line_t *_cmd, uint8_t echoFlg)
{
    ASSERT_PARAM(IS_VALID_ECHO_FLAG(echoFlg));
    
    _cmd->echoFlg = echoFlg;
}

/**
  * @brief  获取命令行回显标志
  *
  * @param  _cmd              : 命令行实例
  *
  * @retval uint8_t           : 回显标志 @ref cmd_line_echo_flg
  *    @arg CMD_LINE_ECHO_ON  : 开启回显
  *    @arg CMD_LINE_ECHO_OFF : 关闭回显
  */
uint8_t cmd_line_get_echo_flg(cmd_line_t *_cmd)
{
    return _cmd->echoFlg;
}

/**
  * @brief  设置命令行运行标志
  *
  * @param  _cmd               : 命令行实例
  *
  * @param  runFlg             : 运行标志 @ref cmd_line_run_flg
  *   @arg  CMD_LINE_RUN_START : 运行
  *   @arg  CMD_LINE_RUN_STOP  : 不运行
  *
  * @retval None
  */
void cmd_line_set_run_flg(cmd_line_t *_cmd, uint8_t runFlg)
{
    ASSERT_PARAM(IS_VALID_RUN_FLAG(runFlg));
    
    _cmd->runFlg = runFlg;
}

/**
  * @brief  获取命令行运行标志
  *
  * @param  _cmd               : 命令行实例
  *
  * @retval uint8_t            : 运行标志 @ref cmd_line_run_flg
  *   @arg  CMD_LINE_RUN_START : 运行
  *   @arg  CMD_LINE_RUN_STOP  : 不运行
  */
uint8_t cmd_line_get_run_flg(cmd_line_t *_cmd)
{
    return _cmd->runFlg;
}

/**
 * @}
 */

/**
 * @}
 */

/**
 * @}
 */
